Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

23장. 전환 중의 데이터 정합성 — Dual-write에서 살아남기

22장에서 우리는 Saga 패턴을 보았다. 이미 분산된 시스템에서 어떻게 정합성을 맞추는지 다뤘다.

하지만 마이크로서비스 전환 중에는 더 까다로운 문제가 있다.

모놀리스와 신규 서비스가 같은 데이터를 동시에 다룬다.

이 공존기는 보통 수개월에서 수년에 걸친다. 그 기간 내내 데이터 정합성을 어떻게 유지할 것인가?

7장에서 데이터 마이그레이션의 큰 그림을 다뤘다. 이 장은 그 안의 가장 위험한 구간에 집중한다.


공존기에 데이터가 어디에 있는가

전환 중 데이터는 보통 다음 상태에 있다.

flowchart LR
    Monolith --> OldDB[(원래 DB)]
    NewService --> NewDB[(새 DB)]
    OldDB -.->|동기화| NewDB
  • 모놀리스는 원래 DB를 본다
  • 신규 서비스는 새 DB를 본다
  • 두 DB 사이에 동기화가 흐른다

이 상태에서 무엇이 위험한가?

두 DB가 어긋날 수 있다. 그리고 한 번 어긋나면 발견하기 어렵다.


동기화의 두 가지 방식

7장에서 본 두 가지 동기화 방식을 다시 보자.

1️⃣ Dual-write

flowchart LR
    App --> OldDB[(원래 DB)]
    App --> NewDB[(새 DB)]

애플리케이션이 양쪽 DB에 동시에 쓴다.

장점:

  • 즉시 반영된다
  • 별도 인프라가 필요 없다

단점:

  • 한쪽 실패 시 정합성이 깨진다
  • 트랜잭션이 두 DB에 걸려 있다
  • 코드가 복잡해진다

2️⃣ CDC (Change Data Capture)

flowchart LR
    App --> OldDB[(원래 DB)]
    OldDB --> CDC[CDC Pipeline]
    CDC --> NewDB[(새 DB)]

DB의 변경 로그를 별도 파이프라인이 따라간다.

장점:

  • 애플리케이션은 한 DB만 안다
  • 비동기로 자연스럽다

단점:

  • 지연이 있다 (수 ms ~ 수 초)
  • 별도 파이프라인 운영 부담

어떤 것을 선택하는가

상황권장
데이터의 즉시 일관성이 중요Dual-write
약간의 지연 허용 가능CDC
원래 DB 스키마를 못 바꾼다CDC
한쪽 실패 시 자동 복구 원함CDC
단순한 구조 우선Dual-write

대부분의 케이스에서 CDC가 더 안전하다.

이유:

  • 애플리케이션이 한 DB만 보면 되므로 코드가 단순하다
  • 두 DB 트랜잭션을 묶을 필요가 없다
  • 12장에서 본 Outbox 패턴과 잘 어울린다

그러나 즉시 일관성이 필요한 영역(예: 잔액, 재고)에서는 Dual-write가 더 적합할 수 있다.


Dual-write의 위험

Dual-write를 선택했다면 다음을 받아들여야 한다.

위험 1 — 한쪽이 실패한다

sequenceDiagram
    participant App
    participant OldDB
    participant NewDB

    App->>OldDB: INSERT (성공)
    App->>NewDB: INSERT (실패)

결과: 원래 DB에는 있고, 새 DB에는 없다.

위험 2 — 순서가 어긋난다

두 트랜잭션이 동시에 양쪽에 쓰면 적용 순서가 달라질 수 있다.

sequenceDiagram
    participant T1
    participant T2
    participant OldDB
    participant NewDB

    T1->>OldDB: UPDATE balance=100
    T2->>OldDB: UPDATE balance=150
    T2->>NewDB: UPDATE balance=150
    T1->>NewDB: UPDATE balance=100

원래 DB: 150 (최신) 새 DB: 100 (옛 값이 덮어씌움)

위험 3 — 부분 트랜잭션

원래 DB의 한 트랜잭션이 여러 테이블을 묶고 있다면 새 DB로 분리할 때 트랜잭션이 깨질 수 있다.


Dual-write를 안전하게 만드는 방법

1️⃣ 한쪽을 진실의 원천으로 정한다

Source of Truth는 항상 한 곳이다.

전환 초기:

  • 원래 DB가 진실
  • 새 DB는 복제본

전환 후반:

  • 새 DB가 진실
  • 원래 DB는 복제본 (롤백용)

진실의 원천이 어디인지를 매 시점 명확히 한다.

2️⃣ 실패는 진실의 원천에서 막는다

진실의 원천이 원래 DB라면

  • 원래 DB 쓰기가 성공해야 응답한다
  • 새 DB 쓰기는 실패해도 무시 (별도 모니터링)
  • 어긋남은 정기 검증으로 잡는다

이렇게 하면 두 DB 트랜잭션을 묶지 않아도 된다.

3️⃣ 새 DB 쓰기는 비동기로

sequenceDiagram
    participant App
    participant OldDB
    participant Queue
    participant Worker
    participant NewDB

    App->>OldDB: INSERT
    App->>Queue: 이벤트 발행
    Worker->>Queue: 수신
    Worker->>NewDB: INSERT

이 구조는 사실상 12장의 Outbox 패턴이다.

  • 애플리케이션은 원래 DB만 신뢰
  • 새 DB는 큐를 통해 따라간다
  • 실패 시 큐에서 재시도

정합성 검증 — 가장 자주 빠지는 부분

Dual-write든 CDC든 자동 정합성 검증 없이는 운영할 수 없다.

검증의 세 단계

1️⃣ 행 수 비교

-- 원래 DB
SELECT COUNT(*) FROM orders;

-- 새 DB
SELECT COUNT(*) FROM orders;

차이가 있으면 즉시 알람.

2️⃣ 샘플링 비교

무작위로 100개의 ID를 골라
두 DB의 해당 행을 비교한다.

* 컬럼 값이 다르면 차이 기록
* 임계치 이상이면 알람

3️⃣ 시계열 드리프트 추적

flowchart LR
    Now[현재] --> Compare[비교]
    OneMinAgo[1분 전] --> Compare
    Compare --> Metric[일치율 메트릭]

매 분 일치율을 측정해서 시간에 따른 변화를 본다.

  • 일치율이 99.9% 미만이면 알람
  • 갑자기 떨어지면 더 큰 문제

드리프트가 발생했을 때

검증에서 차이가 발견되었다. 무엇을 할 것인가?

첫 단계 — 원인 파악

차이의 패턴을 본다.

  • 한 특정 시점 이후에만 차이가 있다 → 그 시점에 무슨 일이?
  • 특정 ID 범위에만 차이 → 특정 코드 경로 의심
  • 무작위 분포 → 동기화 자체의 일시적 실패

둘째 단계 — 진실의 원천 기준으로 보정

진실의 원천이 정해져 있다면 보정은 단순하다.

  • 원천에서 새 DB로 일방향 복원
  • 보정 스크립트 자동화

셋째 단계 — 재발 방지

같은 종류의 드리프트가 다시 안 생기게 한다.

  • 동기화 로직 점검
  • 자동 검증의 빈도 증가
  • 알람 임계치 조정

진짜 위험은 발견 못한 차이다

드리프트는 발견되면 고칠 수 있다.

진짜 위험은

차이가 있는 줄 모르고 신규 DB로 쓰기 전환이 일어나는 것이다.

이걸 막는 단 하나의 방법은

자동 검증을 매 시점 돌리는 것이다.

검증 없이 쓰기 전환은 도박이다.


롤백 시나리오

쓰기 전환 후에도 원래 DB의 동기화를 유지해야 한다.

flowchart LR
    App -->|Primary| NewDB[(새 DB)]
    NewDB -->|Sync| OldDB[(원래 DB)]

이유:

  • 새 DB에 문제가 생길 수 있다
  • 진실의 원천을 다시 원래 DB로 되돌려야 할 수 있다
  • 동기화가 끊겨 있으면 롤백 시점에 데이터를 잃는다

이 동기화는 보통

  • 쓰기 전환 후 1~3개월 유지
  • 충분한 안정화 기간 후 종료

이 장의 핵심

  • 공존기의 가장 큰 위험은 두 DB 사이의 정합성 깨짐이다
  • Dual-write는 즉시지만 위험하고, CDC는 지연이 있지만 안전하다
  • Source of Truth는 항상 한 곳이어야 한다 — 매 시점 명확히
  • Outbox + 큐 + Worker 구조로 Dual-write의 위험을 줄일 수 있다
  • 자동 정합성 검증은 선택이 아니라 필수다
  • 발견된 드리프트는 고칠 수 있지만 발견 못한 드리프트는 못 고친다
  • 쓰기 전환 후에도 일정 기간 역방향 동기화를 유지한다